
# 降维、特征提取与流形学习
'''
无监督学习的数据变换主要用于可视化、数据压缩和寻找更有用的数据表示。
常用的算法包括主成分分析、非负矩阵分解（NMF）和 t-SNE，其中 NMF 用于特征提取，t-SNE 用于二维可视化.
'''
import os

import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# 主成分分析
'''
主成分分析（PCA）是一种降维技术，通过旋转数据集找到一组不相关的主成分。这些主成分是正交的，代表数据在不同方向上的最大方差。PCA 的过程包括：
 旋转数据集：通过计算协方差矩阵的特征向量实现。
 主成分不相关：每个主成分独立，捕捉数据的不同方面。
 选择子集：前几个主成分能解释大部分数据方差，可用于代替原始数据，实现降维。
PCA 以更简洁的形式表示数据，保留重要信息，适用于数据压缩、去噪和可视化等场景。
'''

import mglearn
from matplotlib import pyplot as plt
from sklearn.datasets import load_breast_cancer, fetch_lfw_people
from sklearn.model_selection import train_test_split

# 下面的例子展示了 PCA 对一个模拟二维数据集的作用：
mglearn.plots.plot_pca_illustration()
# plt.show()

'''
第一张图显示的是原始数据点，这些数据点用不同颜色区分，以便于观察它们在空间中的分布情况
找到方差最大的方向（成分 1）：
    PCA 算法首先寻找数据中方差最大的方向，并将其标记为“成分 1”（Component 1）。这个方向是数据中包含最多信息的方向，
    也就是说，沿着这个方向的数据点变化最大，特征之间的相关性最强。换句话说，这个方向捕捉了数据的最主要特征
    
找到正交方向（成分 2）：
    接下来，算法寻找与第一个方向正交（成直角）且包含最多信息的方向。
    在二维空间中，只有一个与第一个方向正交的方向，但在更高维的空间中，会有无穷多个正交方向。
    这个正交方向被标记为“成分 2”（Component 2），它捕捉了数据在与成分 1 正交方向上的主要特征
    
主成分的表示：
    虽然主成分被画成箭头，但箭头的头尾位置并不重要。例如，第一个成分可以画成从中心指向左上，也可以指向右下，因为它们表示的是同一个方向。
    主成分是数据方差的主要方向，因此它们能够有效地捕捉数据的主要特征和结构.
    
主成分的个数：
    一般来说，主成分的个数与原始特征的个数相同。这意味着如果原始数据有n个特征，PCA 会找到n个主成分。
    然而，在实际应用中，通常只需要选择前几个主成分，因为它们能够解释大部分的数据方差，从而实现有效的降维
'''

'''
第二张图
旋转数据：
    第二张图显示的是经过旋转后的数据。PCA 通过旋转坐标轴，使得第一主成分与新的 x 轴平行，第二主成分与新的 y 轴平行。
    这种旋转使得数据在新的坐标系中更容易解释，因为每个主成分代表数据的一个主要方向.

中心化处理：
    在进行旋转之前，PCA 会对数据进行中心化处理，即从每个特征中减去其平均值。这样做的目的是使得变换后的数据以零为中心。
    中心化是 PCA 的一个重要步骤，因为它确保了数据的均值为零，从而使得协方差矩阵的计算更加准确和有意义.

不相关的坐标轴：
    在旋转后的坐标系中，两个坐标轴（即主成分）是不相关的。这意味着在新的数据表示中，除了对角线上的元素（方差），相关矩阵的其他元素全部为零。
    换句话说，每个主成分捕捉了数据的一个独立特征，它们之间没有线性相关性.
'''

'''
降维过程：
    保留主成分：
        在 PCA 中，可以通过仅保留一部分主成分来实现降维。
        在这个例子中，只保留第一个主成分。这意味着将数据从二维降为一维，因为第一个主成分捕捉了数据中最重要的变化方向.
    选择方向：
        保留的第一主成分并不是原始特征之一，而是数据中最有趣的方向（如图中从左上到右下的方向）。
        这个方向代表了数据的最大方差，因此包含了最多的信息.

反向旋转和恢复：
    反向旋转：
        在降维后，可以将数据反向旋转回原始特征空间。这个过程将数据从主成分空间转换回原始特征空间，以便于更好地理解和解释数据.
    恢复平均值：
        在反向旋转后，将之前减去的平均值重新加到数据中。这样可以确保数据的中心位置与原始数据一致，便于进一步的分析和可视化.
应用：
    去噪：
        这种变换有时用于去除数据中的噪声影响，因为通过保留主要的主成分，可以滤除一些不重要的变化或噪声.
    可视化：
        将主成分中保留的信息可视化，可以帮助我们更好地理解数据的主要特征和结构，尤其是在高维数据的情况下，
        通过降维可以将其可视化为二维或三维图形，以便于观察和分析
'''

# 将 PCA 应用于 cancer 数据集并可视化
# PCA 最常见的应用之一就是将高维数据集可视化了，例如查看乳腺癌数据集（这个数据集包含 30 个特征）
'''
利用 PCA，我们可以获取到主要的相互作用，并得到稍为完整的图像。我们可以找到前两个主成分，并在这个新的二维空间中用散点图将数据可视化。
在应用 PCA 之前，我们利用 StandardScaler 缩放数据，使每个特征的方差均为 1：
'''
cancer = load_breast_cancer()
scaler = StandardScaler()
scaler.fit(cancer.data)
X_scaled = scaler.transform(cancer.data)

'''
学习并应用 PCA 变换与应用预处理变换一样简单。我们将 PCA 对象实例化，调用 fit 方法找到主成分，
然后调用 transform 来旋转并降维。默认情况下，PCA 仅旋转（并移动）数据，但保留所有的主成分。
'''


# 为了降低数据的维度，我们需要在创建 PCA 对象时指定想要保留的主成分个数：
# 保留数据的前两个主成分
pca = PCA(n_components=2)
# 对乳腺癌数据拟合PCA模型
pca.fit(X_scaled)
# 将数据变换到前两个主成分的方向上
X_pca = pca.transform(X_scaled)
print("Original shape: {}".format(str(X_scaled.shape)))
print("Reduced shape: {}".format(str(X_pca.shape)))
# Original shape: (569, 30)
# Reduced shape: (569, 2)

# 现在我们可以对前两个主成分作图
# 对第一个和第二个主成分作图，按类别着色
plt.figure(figsize=(8, 8))
mglearn.discrete_scatter(X_pca[:, 0], X_pca[:, 1], cancer.target)
plt.legend(cancer.target_names, loc="best")
plt.gca().set_aspect("equal")
plt.xlabel("First principal component")
plt.ylabel("Second principal component")


'''
PCA 是一种无监督学习方法，通过数据中的相关性寻找旋转方向，不依赖类别信息。
在散点图中，第一和第二主成分的关系显示两个类别在二维空间中被很好地分离，表明线性分类器可以有效区分它们。
此外，恶性点比良性点更分散，这一点也在直方图中得到体现。
'''

'''
PCA 的一个缺点是主成分轴难以解释。主成分是原始特征的复杂线性组合，通常无法直接对应到原始特征的实际含义。
这些主成分会存储在 PCA 对象的 components_ 属性中。
'''

# 特征提取的特征脸
'''
PCA 的另一个应用是特征提取，其核心是将原始数据转换为更适合分析和处理的新表示方式。
在图像处理中，PCA 可以从由像素组成的图像中提取更高级别的特征，以便于后续分析和处理。
'''

'''
PCA 在图像处理中的一个应用是用于特征提取，特别是在处理 Wild 数据集（包含名人脸部图像）时。该数据集涵盖多种类别的名人图像。
在进行 PCA 特征提取时，通常使用灰度图像并缩小图像尺寸以加快处理速度。
PCA 可以从高维人脸图像中提取最具代表性的特征，用于提高人脸识别的准确性和实现图像压缩。
'''

# 指定数据集的存储路径
data_home = os.path.join('/', 'data')
print(data_home)

# fetch_lfw_people用于人脸识别研究的公共数据集，包含许多名人的面部图像
# min_faces_per_person=20：这个参数指定了每个名人至少需要有的面部图像数量。设置为20意味着只有那些至少有20张面部图像的名人才会被包含在数据集中。
# resize=0.7：这个参数用于指定图像的缩放比例。设置为0.7意味着原始图像会被缩小到原来的70%。
# data_home=data_home：这个参数指定了数据集下载和存储的根目录。
# download_if_missing=True：这个参数指示如果在data_home指定的路径下没有找到LFW数据集，是否应该自动下载。
#   设置为True意味着如果数据集不存在，函数会尝试从互联网上下载它
people = fetch_lfw_people(min_faces_per_person=20, resize=0.7,data_home=data_home,download_if_missing=True)
image_shape = people.images[0].shape
fix, axes = plt.subplots(2, 5, figsize=(15, 8),
 subplot_kw={'xticks': (), 'yticks': ()})
for target, image, ax in zip(people.target, people.images, axes.ravel()):
 ax.imshow(image)
 ax.set_title(people.target_names[target])

# plt.show()

# 但这个数据集有些偏斜，其中包含 George W. Bush（小布什）和 Colin Powell（科林 • 鲍威尔）的大量图像
# 为了降低数据偏斜，我们对每个人最多只取 50 张图像（否则，特征提取将会被 George W.Bush 的可能性大大影响）
mask = np.zeros(people.target.shape, dtype=np.bool)
for target in np.unique(people.target):
 mask[np.where(people.target == target)[0][:50]] = 1
X_people = people.data[mask]
y_people = people.target[mask]
# 将灰度值缩放到0到1之间，而不是在0到255之间
# 以得到更好的数据稳定性
X_people = X_people / 255.

'''
人脸识别任务通常涉及判断一张新的人脸是否与数据库中的某个人匹配，这在照片管理、社交媒体和安全系统中很常见。
由于每个人只有少量的图像样本，训练传统分类器较为困难，且希望系统能够灵活地添加新人物。
一种解决方案是使用单一最近邻分类器，它通过找到与新样本最相似的已知人脸来进行分类，适合处理每个类别只有一个样本的情况。
接下来将评估KNeighborsClassifier的性能。
'''
from sklearn.neighbors import KNeighborsClassifier
X_train,X_test,y_train,y_test = train_test_split(X_people,y_people,stratify=y_people,random_state=0)
knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train,y_train)
print("Test set score of 1-nn: {:.2f}".format(knn.score(X_test, y_test)))

'''
PCA 可以改善人脸识别中的相似度度量。直接在原始像素空间计算距离是不理想的，因为人脸图像的微小移动会导致像素级的巨大变化，而这种方式与人类对人脸特征的感知差异很大。
通过 PCA 将数据转换到主成分空间后，距离度量更能反映面部特征的相似性。启用 PCA 的白化选项可以进一步标准化主成分的尺度，使数据呈圆形分布，从而提高相似度度量的精度。
'''

mglearn.plots.plot_pca_whitening()
# 利用启用白化的 PCA 进行数据变换

# 我们对训练数据拟合 PCA 对象，并提取前 100 个主成分。然后对训练数据和测试数据进行变换：
# PCA 创建了一个主成分分析（PCA）对象，用于数据降维和白化处理
# n_components=100：这个参数指定了要保留的主成分的数量。在这里，它被设置为100，意味着PCA将选择前100个主成分来表示数据。
#   这些主成分是原始数据中方差最大的方向，能够捕捉数据的大部分变化。通过减少数据的维度，PCA可以帮助简化模型，减少计算量，同时去除噪声和冗余特征。
# whiten=True：这个参数指示PCA在降维后是否进行白化处理。白化是一种预处理步骤，它将数据转换成一个具有单位方差和零均值的新空间。
#   换句话说，白化后的数据在各个主成分方向上的尺度是一致的，这有助于消除不同特征之间的相关性。
#   在某些机器学习算法中，如神经网络和高斯过程，白化可以提高模型的性能和稳定性，因为它使得数据的分布更接近于标准正态分布。
# random_state=0：这个参数用于设置随机数生成器的种子。PCA算法在初始化过程中可能会使用随机数，例如在随机初始化主成分时。
#   random_state被设置为0，意味着随机数生成器将使用一个固定的种子值。

pca = PCA(n_components=100, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)
print("X_train_pca.shape: {}".format(X_train_pca.shape))

# 新数据有 100 个特征，即前 100 个主成分。现在，可以对新表示使用单一最近邻分类器来
# 将我们的图像分类：

knn = KNeighborsClassifier(n_neighbors=1)
knn.fit(X_train_pca, y_train)
print("Test set accuracy: {:.2f}".format(knn.score(X_test_pca, y_test)))

'''
对于图像数据，我们还可以很容易地将找到的主成分可视化。请记住，成分对应于输入空间里的方向。
这里的输入空间是 87 像素 ×65 像素的灰度图像，所以在这个空间中的方向也是 87 像素 ×65 像素的灰度图像。
'''

# 我们来看一下前几个主成分
print("pca.components_.shape: {}".format(pca.components_.shape))

fix, axes = plt.subplots(3, 5, figsize=(15, 12),
 subplot_kw={'xticks': (), 'yticks': ()})
for i, (component, ax) in enumerate(zip(pca.components_, axes.ravel())):
 ax.imshow(component.reshape(image_shape),cmap='viridis')
 ax.set_title("{}. component".format((i + 1)))

'''
PCA 能够捕捉人脸图像的某些特征（如对比度和明暗差异），但这些特征与人类感知人脸的方式不同。
人类更关注年龄、性别、表情等属性，而这些不易从像素强度中推断。
PCA 基于像素分析，通过旋转数据和去除低方差成分来简化数据表示，本质上是寻找一组新的特征值，将数据点表示为这些主成分的加权组合。
'''


'''
使用 PCA 重建图像时：
 前 10 个主成分可以捕捉图像的基本特征，如人脸方向和整体明暗。
 随着主成分数量增加，重建图像的细节会更丰富。
 如果使用与像素数量相同的主成分，可以完美重建原始图像，因为没有信息丢失。

此外，可以用 PCA 的前两个主成分在散点图中可视化人脸数据集，类似于对癌症数据集的处理。
这种方法能帮助理解数据的主要变化方向，并在二维空间中观察不同人脸之间的关系。
'''

mglearn.discrete_scatter(X_train_pca[:, 0], X_train_pca[:, 1], y_train)
plt.xlabel("First principal component")
plt.ylabel("Second principal component")

'''
使用前两个主成分进行可视化时，数据点聚集在一起，无法清晰区分不同的类别。
这是因为在低维空间中，PCA只能捕捉到人脸的非常基本和粗略的特征，不足以展现类别之间的细微差别。
即使增加到10个主成分，PCA仍然无法充分捕捉人脸的复杂特征，导致类别分界不明显。
'''

